﻿using CQRSFramework.Common.Components;
using CQRSFramework.Commands;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace CQRSFramework.Events.Impl
{
    public class EventBus : IEventBus
    {
        public void Publish<T>(T domainEvent) where T : class, IDomainEvent
        {
            var eventHandlerType = typeof(IEventHandler<>).MakeGenericType(domainEvent.GetType());
            var objectEventHandler = ObjectContainer.ResolveOptional(eventHandlerType);
            if (objectEventHandler == null)
            {
                throw new Exception($"could not find the handler of event. eventType:{domainEvent.GetType().Name}");
            }
            var realEventHandler = objectEventHandler as IEventHandler<T>;
            realEventHandler.HandleAsync(domainEvent);
        }

        public void Publish<T>(IEnumerable<T> domainEvents) where T : class, IDomainEvent
        {
            foreach (var domainEvent in domainEvents)
            {
                var eventHandlerType = typeof(IEventHandler<>).MakeGenericType(domainEvent.GetType());
                var objectEventHandler = ObjectContainer.ResolveOptional(eventHandlerType);
                if (objectEventHandler == null)
                {
                    throw new Exception($"could not find the handler of event. eventType:{domainEvent.GetType().Name}");
                }
                var realEventHandler = objectEventHandler as IEventHandler<T>;
                realEventHandler.HandleAsync(domainEvent);
            }
        }

        public Action<ICommand> BuildAction(IEventHandler handler, Type handlerType, Type messageType)
        {
            var genericType = typeof(IEventHandler<>).MakeGenericType(messageType);
            var parameter = Expression.Parameter(typeof(IEventHandler));
            var methodBody = Expression.Call(
                Expression.Convert(Expression.Constant(handler), genericType),
                handlerType.GetMethod("Handle"),
                Expression.Property(Expression.Convert(parameter, genericType), "Body"));
            return Expression.Lambda<Action<ICommand>>(Expression.Block(methodBody), parameter).Compile();
        }
    }
}
